package de.dhbw.karlsruhe.picsim.model.memory.impl;

import de.dhbw.karlsruhe.picsim.model.cpu.File;
import de.dhbw.karlsruhe.picsim.model.cpu.ReadOnlyFile;
import de.dhbw.karlsruhe.picsim.model.memory.FileAddress;
import de.dhbw.karlsruhe.picsim.model.memory.FileFactory;
import de.dhbw.karlsruhe.picsim.model.memory.FileMemory;
import de.dhbw.karlsruhe.picsim.model.memory.FileMemoryMutable;
import de.dhbw.karlsruhe.picsim.util.impl.BitUtilities;
import de.dhbw.karlsruhe.picsim.util.impl.Lengths;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NotNull;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by IntelliJ IDEA.
 * User: goesen
 * Date: Apr 14, 2010
 * Time: 9:49:40 AM
 */
public final class FileMemoryImpl implements FileMemory {
    private static final Log log = LogFactory.getLog(FileMemoryImpl.class);
    private final Map<FileAddress, File> ourFiles;


    public static FileMemoryMutable create(@NotNull final FileFactory factory) {

        final HashMap<FileAddress, File> files = new HashMap<FileAddress, File>(Lengths.FILE_MEMORY_SIZE);
        //Das indirect wird beim get explezit abgehandelt, deswegen fangen wir mit dem timerRegister an
        putInBankA(files, factory.createNewFile(), 1);
        //optionRegister
        final File option = factory.createNewFile();
        putInBankB(files, option, 1);
        option.setValue(RegisterValueImpl.createSafe(255));

        putInBoth(files, factory.createNewFile(), 2);

        final File status = factory.createNewFile();
        putInBoth(files, status, 3);
        status.setValue(RegisterValueImpl.createUnsafe(24));

        putInBoth(files, factory.createNewFile(), 4);

        putInBankA(files, factory.getPortAFile(), 5);
        putInBankB(files, factory.getTrisAFile(), 5);

        putInBankA(files, factory.getPortBFile(), 6);
        putInBankB(files, factory.getTrisBFile(), 6);
        //todo die Register sind nicht so wichtig
        putInBoth(files, factory.createUnimplementedFile(), 7);
        putInBoth(files, factory.createUnimplementedFile(), 8);
        putInBoth(files, factory.createUnimplementedFile(), 9);
        //die jetzt schon
        putInBoth(files, factory.createNewFile(), 10);
        putInBoth(files, factory.createNewFile(), 11);
        //gpr
        for (int i = 12; i < 48; i++) {
            putInBoth(files, factory.createNewFile(), i);
        }
        for (int i = 48; i < 128; i++) {
            putInBoth(files, factory.createUnimplementedFile(), i);
        }
        assert files.size() == Lengths.FILE_MEMORY_SIZE - 2 : "falsche Anzahl von Registern " + files.size();


        return new FileMemoryImpl(files);
    }

    private static void putInBoth(final Map<FileAddress, File> files, final File file, final int where) {
        putInBankA(files, file, where);
        putInBankB(files, file, where);
    }

    private static void putInBankB(final Map<FileAddress, File> files, final File file, final int where) {
        final FileAddress fileAddress = FileAddressImpl.createUnSafe(where + 0x80);
        files.put(fileAddress, file);
    }

    private static void putInBankA(final Map<FileAddress, File> files, final File file, final int where) {
        final FileAddress fileAddress = FileAddressImpl.createUnSafe(where);
        files.put(fileAddress, file);
    }


    private FileMemoryImpl(@NotNull final Map<FileAddress, File> files) {

        if (files.containsValue(null)) {
            throw new IllegalArgumentException();
        }
        ourFiles = new HashMap<FileAddress, File>(files);
    }

    @NotNull
    @Override
    public File getFile(@NotNull final FileAddress fileAddress) {

        if (isIndirect(fileAddress)) {
            return getIndirectAddressedFile();
        }
        final File result = ourFiles.get(fileAddress);
        if (result == null) {
            log.fatal("File is \"null\" at " + fileAddress);
            throw new IllegalStateException();
        }

        return result;
    }

    private File getIndirectAddressedFile() {
        final ReadOnlyFile fsrFile = getFile(FileAddressImpl.fsrAddress());
        if (fsrFile.getData().asInteger() == 0) {
            // ein nicht implementiertes register
            return getFile(FileAddressImpl.createUnSafe(100));
        } else {
            return getFile(new FileAddressImpl(fsrFile.getData()));
        }
    }

    private boolean isIndirect(final FileAddress fileAddress) {
        final int intValue = BitUtilities.fromLeastSignificantBitFirst(fileAddress.getBits());
        return intValue == 0 || intValue == 128;
    }

}
